package com.example.slidecaptcha2;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.security.SecureRandom;
import java.text.NumberFormat;
import java.util.HashMap;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 滑块验证工具类
 *
 * @author yyf900524
 * @date 2019/08/22
 */
public class ImgVerifyCodeUtil {

    private static int ORI_WIDTH = 320;
    private static int ORI_HEIGHT = 140;
    private static int X;
    private static int Y;
    private static int WIDTH;
    private static int HEIGHT;
    private static float xPercent;
    private static float yPercent;

    private static int[][] hline = {{19,1},{18,2},{17,3},{17,4},{16,5},{16,6},{16,7},{16,8},{16,9},{16,10},{17,11},{18,12},{18,13},{18,14},{18,15},{17,16},{16,17},{14,18},{13,18},{12,18},{11,18},{10,18},{9,18},{8,18},{7,18},{6,18},{5,18},{4,18},{3,18},{2,17},{1,17},{1,18},{1,19},{1,20},{1,21},{1,22},{1,23},{1,24},{1,25},{1,26},{1,27},{1,28},{2,29},{2,30},{3,30},{4,30},{5,30},{6,29},{7,28},{8,28},{9,28},{10,27},{11,27},{12,28},{13,28},{14,28},{15,29},{16,30},{17,31},{17,32},{17,33},{17,34},{17,35},{17,36},{17,37},{16,38},{15,39},{14,40},{13,40},{12,41},{11,41},{10,41},{9,40},{8,40},{7,40},{6,39},{5,38},{4,38},{3,38},{3,39},{2,40},{2,41},{2,42},{2,43},{2,44},{2,45},{2,46},{2,47},{2,48},{2,49},{2,50},{2,51},{2,52},{2,53},{3,54},{3,55},{3,56},{4,56},{5,55},{6,54},{7,54},{8,54},{9,54},{10,54},{11,54},{12,53},{13,53},{14,54},{15,54},{16,54},{17,54},{18,54},{19,54},{20,55},{21,56},{21,57},{21,58},{21,59},{20,60},{20,61},{20,62},{19,63},{20,63},{21,63},{22,63},{23,63},{24,63},{25,63},{26,63},{27,63},{28,63},{29,63},{30,63},{31,63},{32,63},{32,62},{32,61},{31,60},{31,59},{30,58},{30,57},{30,56},{30,55},{31,54},{32,53},{33,53},{34,52},{35,52},{36,52},{37,52},{38,52},{39,52},{40,52},{40,51},{40,17},{40,18},{40,19},{40,20},{40,21},{40,22},{40,23},{40,24},{40,25},{40,26},{40,27},{40,28},{40,29},{40,30},{40,31},{40,32},{40,33},{40,34},{40,35},{40,36},{40,37},{40,38},{40,39},{40,40},{40,41},{40,42},{40,43},{40,44},{40,45},{40,46},{40,47},{40,48},{40,49},{40,50},{39,17},{38,17},{37,17},{36,17},{35,17},{34,17},{33,17},{32,17},{31,17},{30,16},{29,16},{28,16},{27,15},{26,15},{25,14},{25,13},{26,11},{27,10},{27,9},{28,8},{28,7},{28,6},{28,5},{27,4},{27,3},{26,2},{25,1},{24,1},{23,1},{22,1},{21,1},{20,1}};
    private static final Logger LOGGER = LoggerFactory.getLogger(ImgVerifyCodeUtil.class);

    public static int getX() {
        return X;
    }

    public static int getY() {
        return Y;
    }

    public static float getXPercent() {
        return xPercent;
    }

    public static float getYPercent() {
        return yPercent;
    }

    /**
     * 根据模板切图
     *
     * @param templateFile
     * @param targetFile
     * @param templateType
     * @param targetType
     * @return
     * @throws Exception
     */
    public static synchronized ConcurrentHashMap<String, Object> pictureTemplatesCut(File templateFile, File targetFile, String templateType, String targetType) throws Exception {
        ConcurrentHashMap<String, Object> pictureMap = new ConcurrentHashMap<>();
        // 文件类型
        String templateFiletype = templateType;
        String oriFiletype = targetType;
        if (StringUtils.isBlank(templateFiletype) || StringUtils.isBlank(oriFiletype)) {
            throw new RuntimeException("file type is empty");
        }
        // 源文件流
        File Orifile = targetFile;

        BufferedImage imageTemplate = ImageIO.read(templateFile);

        // 模板图
        WIDTH = imageTemplate.getWidth();
        HEIGHT = imageTemplate.getHeight();
        generateCutoutCoordinates();

        // 最终图像
        BufferedImage newImage = new BufferedImage(WIDTH, HEIGHT, imageTemplate.getType());
        Graphics2D graphics = newImage.createGraphics();
        graphics.setBackground(Color.white);

        int bold = 5;
        // 获取感兴趣的目标区域
        InputStream is = null;
        BufferedImage targetImageNoDeal = null;
        try {
            is = new FileInputStream(Orifile);
            targetImageNoDeal = getTargetArea(X, Y, WIDTH, HEIGHT, is, oriFiletype);
        } catch (Exception e1) {
            LOGGER.error("创建流发生异常", e1);
            throw e1;
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e2) {
                    LOGGER.error("关闭流发生异常", e2);
                }
            }
        }

        // 根据模板图片抠图
        newImage = DealCutPictureByTemplate(targetImageNoDeal, imageTemplate, newImage);

        // 设置“抗锯齿”的属性
        graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        graphics.setStroke(new BasicStroke(bold, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
        graphics.drawImage(newImage, 0, 0, null);
        graphics.dispose();

        ByteArrayOutputStream os = new ByteArrayOutputStream(); // 新建流。
        ImageIO.write(newImage, "png", os); // 利用ImageIO类提供的write方法，将bi以png图片的数据模式写入流。
        byte[] newImages = os.toByteArray();
        pictureMap.put("newImage", newImages);

        // 源图生成遮罩
        BufferedImage oriImage = ImageIO.read(Orifile);
        byte[] oriCopyImages = DealOriPictureByTemplate(oriImage, imageTemplate, X, Y);
        pictureMap.put("oriCopyImage", oriCopyImages);

        HashMap xyPoint = new HashMap<String, Integer>();
        xyPoint.put("x", getX());
        xyPoint.put("y", getY());
        pictureMap.put("xyPoint", xyPoint);
        return pictureMap;
    }

    /**
     * 抠图后原图生成
     *
     * @param oriImage
     * @param templateImage
     * @param x
     * @param y
     * @return
     * @throws Exception
     */
    private static byte[] DealOriPictureByTemplate(BufferedImage oriImage, BufferedImage templateImage, int x, int y) throws Exception {
        // 源文件备份图像矩阵 支持alpha通道的rgb图像
        BufferedImage ori_copy_image = new BufferedImage(oriImage.getWidth(), oriImage.getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
        // 源文件图像矩阵
        int[][] oriImageData = getData(oriImage);
        // 模板图像矩阵
        int[][] templateImageData = getData(templateImage);

        //copy 源图做不透明处理
        for (int i = 0; i < oriImageData.length; i++) {
            for (int j = 0; j < oriImageData[0].length; j++) {
                int rgb = oriImage.getRGB(i, j);
                int r = (0xff & rgb);
                int g = (0xff & (rgb >> 8));
                int b = (0xff & (rgb >> 16));
                //无透明处理
                rgb = r + (g << 8) + (b << 16) + (255 << 24);
                ori_copy_image.setRGB(i, j, rgb);
            }
        }
        int[] values = new int[9];
        int[][] martrix = new int[3][3];
        for (int i = 0; i < templateImageData.length - 5; i++) {
            for (int j = 0; j < templateImageData[i].length - 5; j++) {
                int rgb = templateImage.getRGB(i, j);
                // 对源文件备份图像(x+i,y+j)坐标点进行透明处理
                if (rgb != 16777215 && rgb <= 0) {
                    readPixel(ori_copy_image, x + i, y + j, values);
                    fillMatrix(martrix, values);
                    ori_copy_image.setRGB(x + i, y + j, avgMatrix(martrix));
                } else {
                }
            }
        }
        // 新建流。
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        // 利用ImageIO类提供的write方法，将bi以png图片的数据模式写入流。
        ImageIO.write(ori_copy_image, "png", os);
        // 从流中获取数据数组。
        byte[] b = os.toByteArray();
        return b;
    }


    /**
     * 根据模板图片抠图
     *
     * @param oriImage
     * @param templateImage
     * @return
     */

    private static BufferedImage DealCutPictureByTemplate(BufferedImage oriImage, BufferedImage templateImage, BufferedImage targetImage) throws Exception {
        // 源文件图像矩阵
        int[][] oriImageData = getData(oriImage);
        // 模板图像矩阵
        int[][] templateImageData = getData(templateImage);
        // 模板图像宽度
        for (int i = 0; i < templateImageData.length - 5; i++) {
            // 模板图片高度
            for (int j = 0; j < templateImageData[i].length - 5; j++) {
                // 如果模板图像当前像素点不是白色 copy源文件信息到目标图片中
                int rgb = templateImageData[i][j];

                if (rgb != 16777215 && rgb <= 0) {
                    int arr = oriImageData[i][j];
                    targetImage.setRGB(i, j, arr);
                }
            }
        }
        return edgeExtract1(targetImage);
    }

    /**
     * 小图边缘加黑边
     * @param image
     * @return
     */
    private static BufferedImage edgeExtract1(BufferedImage image) {
        try {
            for (int[] s: hline) {
                //System.out.println("横坐标="+s[0]+",纵坐标="+s[1]);
                image.setRGB(s[1],s[0],Color.GRAY.getRGB());
            }
        }catch (Exception e ){
            e.printStackTrace();
        }

        return image;
    }
    /**
     * 获取目标区域
     *
     * @param x            随机切图坐标x轴位置
     * @param y            随机切图坐标y轴位置
     * @param targetWidth  切图后目标宽度
     * @param targetHeight 切图后目标高度
     * @param ois          源文件输入流
     * @return
     * @throws Exception
     */
    private static BufferedImage getTargetArea(int x, int y, int targetWidth, int targetHeight, InputStream ois, String fileType) throws Exception {
        Iterator<ImageReader> imageReaderList = ImageIO.getImageReadersByFormatName(fileType);
        ImageReader imageReader = imageReaderList.next();
        // 获取图片流
        ImageInputStream iis = ImageIO.createImageInputStream(ois);
        // 输入源中的图像将只按顺序读取
        imageReader.setInput(iis, true);

        ImageReadParam param = imageReader.getDefaultReadParam();
        Rectangle rec = new Rectangle(x, y, targetWidth, targetHeight);
        param.setSourceRegion(rec);
        BufferedImage targetImage = imageReader.read(0, param);
        return targetImage;
    }

    /**
     * 生成图像矩阵
     *
     * @param
     * @return
     * @throws Exception
     */
    private static int[][] getData(BufferedImage bimg) throws Exception {
        int[][] data = new int[bimg.getWidth()][bimg.getHeight()];
        for (int i = 0; i < bimg.getWidth(); i++) {
            for (int j = 0; j < bimg.getHeight(); j++) {
                data[i][j] = bimg.getRGB(i, j);
            }
        }
        return data;
    }

    /**
     * 随机生成抠图坐标
     */
    private static void generateCutoutCoordinates() {
        SecureRandom random = new SecureRandom();
        int widthDifference = ORI_WIDTH - WIDTH;
        int heightDifference = ORI_HEIGHT - HEIGHT;

        if (widthDifference <= 0) {
            X = 5;

        } else {
            X = random.nextInt(ORI_WIDTH - WIDTH) + 5;
        }

        if (heightDifference <= 0) {
            Y = 5;
        } else {
            Y = random.nextInt(ORI_HEIGHT - HEIGHT) + 5;
        }
        NumberFormat numberFormat = NumberFormat.getInstance();
        numberFormat.setMaximumFractionDigits(2);

        xPercent = Float.parseFloat(numberFormat.format((float) X / (float) ORI_WIDTH));
        yPercent = Float.parseFloat(numberFormat.format((float) Y / (float) ORI_HEIGHT));
    }

    private static void readPixel(BufferedImage img, int x, int y, int[] pixels) {
        int xStart = x - 1;
        int yStart = y - 1;
        int current = 0;
        for (int i = xStart; i < 3 + xStart; i++) {
            for (int j = yStart; j < 3 + yStart; j++) {
                int tx = i;
                if (tx < 0) {
                    tx = -tx;
                } else if (tx >= img.getWidth()) {
                    tx = x;
                }
                int ty = j;
                if (ty < 0) {
                    ty = -ty;
                } else if (ty >= img.getHeight()) {
                    ty = y;
                }
                pixels[current++] = img.getRGB(tx, ty);

            }
        }
    }

    private static void fillMatrix(int[][] matrix, int[] values) {
        int filled = 0;
        for (int i = 0; i < matrix.length; i++) {
            int[] x = matrix[i];
            for (int j = 0; j < x.length; j++) {
                x[j] = values[filled++];
            }
        }
    }

    private static int avgMatrix(int[][] matrix) {
        int r = 0;
        int g = 0;
        int b = 0;
        for (int i = 0; i < matrix.length; i++) {
            int[] x = matrix[i];
            for (int j = 0; j < x.length; j++) {
                if (j == 1) {
                    continue;
                }
                Color c = new Color(x[j]);
                r += c.getRed();
                g += c.getGreen();
                b += c.getBlue();
            }
        }
        return new Color(r / 8, g / 8, b / 8).getRGB();
    }
}


